package com.smartmail.receiving.pop3;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import javax.mail.FetchProfile;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.UIDFolder;
import javax.mail.internet.MimeMessage;

import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.smartmail.accounts.MailAccount;
import com.smartmail.mime.MessageIdentificator;
import com.smartmail.receiving.AbstractMessageHandler;
import com.smartmail.receiving.IncomingMailDBManager;
import com.smartmail.receiving.Receiving;
import com.smartmail.receiving.ReceivingRequestsHandler;
import com.smartmail.receiving.RetrievalService;
import com.smartmail.utility.MailDateUtility;
import com.smartmail.utility.MessageUtility;
import com.sun.mail.pop3.POP3Folder;

/**
 * Task of checking POP3 mailbox for new messagess arrival.
 * 
 * @author Tomek
 *
 */
public class POP3MailBoxCheckTask implements Runnable {
	
	/**
	 * Public constructor.
	 * 
	 * @param account -  Mail Account which will be synchronized
	 * @param context context - Application context
	 */
	public POP3MailBoxCheckTask(MailAccount account, Context context){
		
		_mailAccount = account;
		_context = context;
	}

	/**
	 * 1. Connect to the mailbox INBOX folder
	 * 2. Fetch UID field from all messages of INBOX folder
	 * 3. Retrieve from database identificators of messages to delete, and mark appropriate messages on server
	 * 4. Get from mailbox only messages with unknown before UID field or without specified UID
	 * 5. This chosen messages handle with IMAPMessageHandler object, and check it they are 'new'.
	 * 6. It there is more than 0 'new' messages send intent
	 * 7. All messages which was marked as deleted on server, remove from 'to-delete' table in device
	 */
	@Override
	public void run() {
		
		//clear counter of new messages
		counter = 0;
		
		List<MessageIdentificator> markedAsDeleted = null;
		
		Log.d(_logTag, "POP3MailBoxCheckTask : started, " + _mailAccount.getMailAddress());

		try {
			
			messageHandler = new POP3MessageHandler(_context, _mailAccount);

			/**
			 * connect to the store and obtain inbox folder
			 */
			Properties props = new Properties();
			
			//set connection timeout to 7 secods
			props.put("mail.pop3.timeout", 7000);
			props.put("mail.pop3s.timeout", 7000);
			
			props.putAll(_mailAccount.getAccountProperties());

			Session session = Session.getInstance(props,
					_mailAccount.getPasswordAuthentication());
			
			Store store = session.getStore();

			store.connect(_mailAccount.getIncomingMailServerAddress(),
					_mailAccount.getAccountUsername(),
					_mailAccount.getAccountPassword());

			Folder folder = store.getDefaultFolder();
			folder = store.getFolder("INBOX");
			folder.open(Folder.READ_WRITE);
			
			Message messages[] = folder.getMessages();
			//initial identificators downloading
			FetchProfile fp = new FetchProfile();
			fp.add( UIDFolder.FetchProfileItem.UID );
			folder.fetch( messages, fp );
			com.sun.mail.pop3.POP3Folder popFolder = (com.sun.mail.pop3.POP3Folder)folder;
			
			/**
			 * retrieve from database info about messages to be delete from server
			 * and set delete flag on server for this messages
			 */
			
			IncomingMailDBManager manager = Receiving.getDBManager(_mailAccount);
			List<MessageIdentificator> toDeleteIdentificators = manager.getIdentifiersOfMessagesToDelete();
			
			markedAsDeleted = markMessagesToDelete(messages, popFolder, toDeleteIdentificators);
				

			/**
			 * get all messages from INBOX folder and check whether is there any
			 * new messages, if there is, write it to database and send
			 * broadcast intent about new message
			 */
			
			
			// date of last inbox checking
			// decrease it by 2 hours
			Date lastChecked = ReceivingRequestsHandler.getLastInBoxCheckingDate(_mailAccount);
			long lcSeconds; 
			if(lastChecked != null)
				lcSeconds = (lastChecked.getTime()/1000L) - 2*MailDateUtility.HOUR;
			else
				lcSeconds = 0;
			lastChecked = new Date(lcSeconds*1000L);
			
			// list of all messages UIDs of messages stored in device
			List<String> messagesUIDs = Receiving.getDBManager(_mailAccount).getAllMessageUIDs();
			
			// list of messages to check
			List<Message> messagesToCheck = new ArrayList<Message>();
			
			for(Message m : messages){
				
				String msgUID = popFolder.getUID(m);
				if( msgUID == null || !messagesUIDs.contains(msgUID) )
					messagesToCheck.add(m);
			}
			
			Collections.reverse(messagesToCheck);
			
			Log.d(_logTag, "POP3MailBoxCheckTask : messages to check " + messagesToCheck.size());

			for(Message m : messagesToCheck){
				
				// check if this message is sent after last inbox checking
				if( m.getSentDate() == null || m.getSentDate().after(lastChecked)){
					
					// every message should be of Mime type
					if (m!= null && m instanceof MimeMessage) {

						MimeMessage mimeMessage = (MimeMessage) m;
						
						// check if this is new message
						int result = messageHandler.handle(mimeMessage,popFolder.getUID(m));
						if(result == AbstractMessageHandler.HANDLE_NEW_MESSAGE || result == AbstractMessageHandler.HANDLE_NEW_MESSAGE_DOWNLOADED)
							counter++;
						
					} else {
	
						Log.wtf(_logTag,
								"POP3RetrievalService : this is non-mime message!");
					}
					
				}
			}

			

			folder.close(true);
			store.close();

		} catch (MessagingException e) {

			for (StackTraceElement ste : e.getStackTrace()) {
				Log.d(RetrievalService._logTag, ste.toString());
			}

			Log.d(_logTag, "POP3RetrievalService : " + e.getMessage() + " "+ e.getCause());
			//return;
			
		} catch (Exception e) {

			Log.d(_logTag, e.getMessage() + e.toString());
			Log.d(_logTag, "POP3RetrievalService : exception other the MessaginException");
			for(StackTraceElement msg : e.getStackTrace())
				Log.d(_logTag, msg.toString() );
			//return;
					
		} finally{
			
			if(counter>0){
				
				/**
				 * send new intent to notification manager
				 */
				Intent intent = new Intent(Receiving.NOTIFICATION_NEW_EMAILS_INTENT);
				intent.putExtra(Receiving.MAIL_INFO_ACCOUNT_ID, _mailAccount.getAccountID());
				intent.putExtra(Receiving.NUMBER_OF_NEW_EMAILS, counter);
				_context.sendBroadcast(intent);
			}
			
			/**
			 * all messages which has been marked as deleted and was deleted from server , delete
			 * from "to delete" table on device
			 * 
			 */
			if( markedAsDeleted != null ){
				
				for(MessageIdentificator ident : markedAsDeleted){
					
					Receiving.getDBManager(_mailAccount).removeFromToDeleteTable(ident);
				}
			}
			Log.d(_logTag, "POP3MailBoxCheckTask : ended, " + _mailAccount.getMailAddress());
		}
		
		
		

	}
	
	
	private List<MessageIdentificator> markMessagesToDelete(Message[] messages, POP3Folder folder, List<MessageIdentificator> msgsToDeleteIdents)
		throws MessagingException
	{
		
		if( msgsToDeleteIdents == null || msgsToDeleteIdents.size() == 0)
			return null;
		
		List<MessageIdentificator> markedAsDeletedIdentificators = new ArrayList<MessageIdentificator>();
		
		for(Message message : messages){
	
			String messageUID = folder.getUID(message);
			if(messageUID != null){
				
				/**
				 * check if this message is on the list of messages to delete
				 * retrieved from database
				 */
				MessageIdentificator toDeleteIdent = null;
				for(MessageIdentificator ident : msgsToDeleteIdents){
					
					if( ident.getMessageUID().equals(messageUID)){
						
						toDeleteIdent = ident;
						markedAsDeletedIdentificators.add(ident);
						message.setFlag(Flags.Flag.DELETED, true);
						Log.d(_logTag, "POP3MailBoxCheckTask : flag set " + messageUID);
						break;
					}
				}
				if(toDeleteIdent != null)
					msgsToDeleteIdents.remove(toDeleteIdent);
				
			}
			// no available uid, should be computed message hash
			else{
				
				/**
				 * check if this message is on the list of messages to delete
				 * retrieved from database
				 */
				MessageIdentificator toDeleteIdent = null;
				String messageHash = MessageUtility.getHash((MimeMessage)message);
				for(MessageIdentificator ident : msgsToDeleteIdents){
					
					
					if( ident.getMessageHash().equals(messageHash)){
						
						toDeleteIdent = ident;
						markedAsDeletedIdentificators.add(ident);
						message.setFlag(Flags.Flag.DELETED, true);
						Log.d(_logTag, "POP3MailBoxCheckTask : flag set " + messageHash);
						break;
					}
				}
				if(toDeleteIdent != null)
					msgsToDeleteIdents.remove(toDeleteIdent);
				
			}
			
		}
		
		/**
		 * to deleted identificators add all which are not fount in inbox folder
		 */
		markedAsDeletedIdentificators.addAll(msgsToDeleteIdents);
		
		return markedAsDeletedIdentificators;
		
		
	}
	
	/**
	 * Mail Account which will be synchronized
	 */
	private MailAccount _mailAccount;
	
	/**
	 * logTag for logging purposes
	 */
	private static String _logTag = "com.smartmail.receiving.pop3";
	
	/**
	 * counter of new messages in mail box
	 */
	private int counter = 0;
	
	/**
	 * Application context
	 */
	private Context _context;
	
	/**
	 * POP3MessageHandler object to handle potential new messages
	 */
	private AbstractMessageHandler messageHandler;

}
